/*-------------------------------------------------------------------------
 *
 * read.c
 *	  将字符串（合法的ASCII表示的节点）转换回节点的例程
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/nodes/read.c
 *
 * HISTORY
 *	  AUTHOR			DATE			MAJOR EVENT
 *	  Andrew Yu			Nov 2, 1994		文件创建
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include <ctype.h>

#include "common/string.h"
#include "nodes/pg_list.h"
#include "nodes/readfuncs.h"
#include "nodes/value.h"


/* pg_strtok 的静态状态 */
static const char *pg_strtok_ptr = NULL;

/* 状态标志决定 readfuncs.c 如何处理位置字段 */
#ifdef WRITE_READ_PARSE_PLAN_TREES
bool		restore_location_fields = false;
#endif


/*
 * stringToNode -
 *	  从其字符串表示构建 Node 树（假定有效）
 *
 * restore_loc_fields 指示 readfuncs.c 是否恢复位置
 * 字段，而不是将它们设置为 -1。 这目前仅在设置了
 * WRITE_READ_PARSE_PLAN_TREES 调试标志的构建中受支持。
 */
static void * fc_stringToNodeInternal(const char *fc_str, bool fc_restore_loc_fields)
{
	void	   *fc_retval;
	const char *fc_save_strtok;
#ifdef WRITE_READ_PARSE_PLAN_TREES
	bool		fc_save_restore_location_fields;
#endif

	/*
	 * 我们保存和恢复 pg_strtok 的预先存在状态。这使得
	 * 对 stringToNode 的可重入调用安全，而不需要
	 * 通过所有的 readfuncs.c 代码传递下一个字符
	 * 指针来承担大量的符号开销。
	 */
	fc_save_strtok = pg_strtok_ptr;

	pg_strtok_ptr = fc_str;		/* 将 pg_strtok 指向要读取的字符串 */

	/*
	 * 如果启用，则同样保存/恢复位置字段处理标志。
	 */
#ifdef WRITE_READ_PARSE_PLAN_TREES
	fc_save_restore_location_fields = restore_location_fields;
	restore_location_fields = fc_restore_loc_fields;
#endif

	fc_retval = nodeRead(NULL, 0); /* 进行读取 */

	pg_strtok_ptr = fc_save_strtok;

#ifdef WRITE_READ_PARSE_PLAN_TREES
	restore_location_fields = fc_save_restore_location_fields;
#endif

	return fc_retval;
}

/*
 * 外部可见的入口点
 */
void * stringToNode(const char *fc_str)
{
	return fc_stringToNodeInternal(fc_str, false);
}

#ifdef WRITE_READ_PARSE_PLAN_TREES

void * stringToNodeWithLocations(const char *fc_str)
{
	return fc_stringToNodeInternal(fc_str, true);
}

#endif


/*****************************************************************************
 *
 * lisp 令牌解析器
 *
 *****************************************************************************/

/*
 * pg_strtok --- 从字符串中检索下一个“标记”。
 *
 * 工作方式有点像strtok，但它从不修改源字符串。
 * （它不是在字符串中存储空字符，而是将标记的长度返回给调用者。）
 * 此外，关于什么是标记的规则是硬编码的，而不是通过传递终止字符的集合进行配置。
 *
 * 假设字符串已经通过stringToNode初始化。
 *
 * 标记的规则是：
 *	* 空白字符（空格、制表符、换行符）始终分隔标记。
 *	* 字符 '(', ')', '{', '}' 即使没有空白字符环绕，也形成单独的标记。
 *	* 否则，标记是直到下一个空白字符或四个特殊字符之一的所有字符。
 *	* 反斜杠 '\' 可用于引号空白或四个特殊字符之一，以便将其作为普通标记字符处理。
 *	  反斜杠本身也必须使用反斜杠转义以保持一致性。
 *	  任何其他字符可以被，但不需要被反斜杠转义。
 *	* 如果结果标记是 '<>'（没有反斜杠），它将作为非NULL指针返回给标记，但长度 == 0。请注意，
 *	  没有其他方式获得零长度的标记。
 *
 * 返回指向下一个标记起始位置的指针，以及标记的长度（包括任何嵌入的反斜杠）存在 *length 中。如果没有
 * 更多标记，返回NULL和0。
 *
 * 注意：此例程不删除反斜杠；如果需要，调用者必须自行处理（参见“debackslash”）。
 *
 * 注意：在7.0版本之前，此例程还具有特殊情况，将以 '"' 开头的标记视为扩展到下一个 '"'。
 * 然而，这段代码是有问题的，因为它无法处理包含嵌入的 '"' 的字符串。因此我已删除此特殊情况，并
 * 引入使用反斜杠对字符进行引号的规则。更高级别的代码应将反斜杠添加到字符串常量，以确保它被视为单个标记。
 */
const char * pg_strtok(int *fc_length)
{
	const char *fc_local_str;		/* 指向字符串的工作指针 */
	const char *fc_ret_str;		/* 要返回的标记起始位置 */

	fc_local_str = pg_strtok_ptr;

	while (*fc_local_str == ' ' || *fc_local_str == '\n' || *fc_local_str == '\t')
		fc_local_str++;

	if (*fc_local_str == '\0')
	{
		*fc_length = 0;
		pg_strtok_ptr = fc_local_str;
		return NULL;			/* 没有更多标记 */
	}

	/*
	 * 现在指向下一个标记的起始位置。
	 */
	fc_ret_str = fc_local_str;

	if (*fc_local_str == '(' || *fc_local_str == ')' ||
		*fc_local_str == '{' || *fc_local_str == '}')
	{
		/* 特殊的一字符标记 */
		fc_local_str++;
	}
	else
	{
		/* 正常标记，可能包含反斜杠 */
		while (*fc_local_str != '\0' &&
			   *fc_local_str != ' ' && *fc_local_str != '\n' &&
			   *fc_local_str != '\t' &&
			   *fc_local_str != '(' && *fc_local_str != ')' &&
			   *fc_local_str != '{' && *fc_local_str != '}')
		{
			if (*fc_local_str == '\\' && fc_local_str[1] != '\0')
				fc_local_str += 2;
			else
				fc_local_str++;
		}
	}

	*fc_length = fc_local_str - fc_ret_str;

	/* 识别“空”标记的特殊情况 */
	if (*fc_length == 2 && fc_ret_str[0] == '<' && fc_ret_str[1] == '>')
		*fc_length = 0;

	pg_strtok_ptr = fc_local_str;

	return fc_ret_str;
}

/*
 * debackslash -
 *	  创建一个指向给定标记的palloc'd字符串。
 *	  标记中的任何保护性反斜杠都会被删除。
 */
char * debackslash(const char *fc_token, int fc_length)
{
	char	   *fc_result = palloc(fc_length + 1);
	char	   *fc_ptr = fc_result;

	while (fc_length > 0)
	{
		if (*fc_token == '\\' && fc_length > 1)
			fc_token++, fc_length--;
		*fc_ptr++ = *fc_token++;
		fc_length--;
	}
	*fc_ptr = '\0';
	return fc_result;
}

#define RIGHT_PAREN (1000000 + 1)
#define LEFT_PAREN	(1000000 + 2)
#define LEFT_BRACE	(1000000 + 3)
#define OTHER_TOKEN (1000000 + 4)

/*
 * nodeTokenType -
 *	  返回包含在标记中的节点标记类型。
 *	  返回以下有效NodeTags之一：
 *		T_Integer, T_Float, T_Boolean, T_String, T_BitString
 *	  以及它自己的某些类型：
 *		RIGHT_PAREN, LEFT_PAREN, LEFT_BRACE, OTHER_TOKEN
 *
 *	  假设：ascii表示是合法的
 */
static NodeTag
fc_nodeTokenType(const char *fc_token, int fc_length)
{
	NodeTag		fc_retval;
	const char *fc_numptr;
	int			fc_numlen;

	/*
	 * 检查标记是否为数字
	 */
	fc_numptr = fc_token;
	fc_numlen = fc_length;
	if (*fc_numptr == '+' || *fc_numptr == '-')
		fc_numptr++, fc_numlen--;
	if ((fc_numlen > 0 && isdigit((unsigned char) *fc_numptr)) ||
		(fc_numlen > 1 && *fc_numptr == '.' && isdigit((unsigned char) fc_numptr[1])))
	{
		/*
		 * 是的。确定它是整数还是浮点数；这需要进行
		 * 语法检查和范围检查。strtoint()可以为
		 * 我们做这两件事。我们知道标记将以
		 * strtoint 会停止的字符结束，因此我们不需要修改字符串。
		 */
		char	   *fc_endptr;

		errno = 0;
		(void) strtoint(fc_token, &fc_endptr, 10);
		if (fc_endptr != fc_token + fc_length || errno == ERANGE)
			return T_Float;
		return T_Integer;
	}

	/*
	 * 这三种情况不需要长度检查，因为pg_strtok()将始终将它们视为单字节标记
	 */
	else if (*fc_token == '(')
		fc_retval = LEFT_PAREN;
	else if (*fc_token == ')')
		fc_retval = RIGHT_PAREN;
	else if (*fc_token == '{')
		fc_retval = LEFT_BRACE;
	else if ((fc_length == 4 && strncmp(fc_token, "true", 4) == 0) ||
			 (fc_length == 5 && strncmp(fc_token, "false", 5) == 0))
		fc_retval = T_Boolean;
	else if (*fc_token == '"' && fc_length > 1 && fc_token[fc_length - 1] == '"')
		fc_retval = T_String;
	else if (*fc_token == 'b')
		fc_retval = T_BitString;
	else
		fc_retval = OTHER_TOKEN;
	return fc_retval;
}

/*
 * nodeRead -
 *	  略高层次的读取器。
 *
 * 此例程在纯词法标记器pg_strtok()之上应用了一些语义知识。
 * 它可以读取
 *	* 值标记节点（整数、浮点数、布尔值或字符串）；
 *	* 一般节点（通过readfuncs.c中的parseNodeString()）；
 *	* 上述列表；
 *	* 整数或OID的列表。
 * 返回值声明为void *，而不是Node *，以避免在为不同类型字段赋值的调用者中显式进行强制转换。
 *
 * 外部调用者应始终为参数传递NULL/0。内部在上层递归级别已经扫描节点表示的第一个标记时，可以传递非NULL标记。
 *
 * 我们假设pg_strtok已经用待读取的字符串初始化（因此此函数仅应从stringToNode操作内部调用）。
 */
void * nodeRead(const char *fc_token, int fc_tok_len)
{
	Node	   *fc_result;
	NodeTag		fc_type;

	if (fc_token == NULL)			/* 需要读取一个标记？ */
	{
		fc_token = pg_strtok(&fc_tok_len);

		if (fc_token == NULL)		/* 输入结束 */
			return NULL;
	}

	fc_type = fc_nodeTokenType(fc_token, fc_tok_len);

	switch ((int) fc_type)
	{
		case LEFT_BRACE:
			fc_result = parseNodeString();
			fc_token = pg_strtok(&fc_tok_len);
			if (fc_token == NULL || fc_token[0] != '}')
				elog(ERROR, "did not find '}' at end of input node");
			break;
		case LEFT_PAREN:
			{
				List	   *fc_l = NIL;

				/*----------
				 * 可能是整数列表：	(i int int ...)
				 * 或OID列表：				(o int int ...)
				 * 或节点/值列表：	(node node ...)
				 *----------
				 */
				fc_token = pg_strtok(&fc_tok_len);
				if (fc_token == NULL)
					elog(ERROR, "unterminated List structure");
				if (fc_tok_len == 1 && fc_token[0] == 'i')
				{
					/* 整数列表 */
					for (;;)
					{
						int			fc_val;
						char	   *fc_endptr;

						fc_token = pg_strtok(&fc_tok_len);
						if (fc_token == NULL)
							elog(ERROR, "unterminated List structure");
						if (fc_token[0] == ')')
							break;
						fc_val = (int) strtol(fc_token, &fc_endptr, 10);
						if (fc_endptr != fc_token + fc_tok_len)
							elog(ERROR, "unrecognized integer: \"%.*s\"",
								 fc_tok_len, fc_token);
						fc_l = lappend_int(fc_l, fc_val);
					}
				}
				else if (fc_tok_len == 1 && fc_token[0] == 'o')
				{
					/* OID列表 */
					for (;;)
					{
						Oid			fc_val;
						char	   *fc_endptr;

						fc_token = pg_strtok(&fc_tok_len);
						if (fc_token == NULL)
							elog(ERROR, "unterminated List structure");
						if (fc_token[0] == ')')
							break;
						fc_val = (Oid) strtoul(fc_token, &fc_endptr, 10);
						if (fc_endptr != fc_token + fc_tok_len)
							elog(ERROR, "unrecognized OID: \"%.*s\"",
								 fc_tok_len, fc_token);
						fc_l = lappend_oid(fc_l, fc_val);
					}
				}
				else
				{
					/* 其他节点类型的列表 */
					for (;;)
					{
						/* 我们已经扫描了下一个标记... */
						if (fc_token[0] == ')')
							break;
						fc_l = lappend(fc_l, nodeRead(fc_token, fc_tok_len));
						fc_token = pg_strtok(&fc_tok_len);
						if (fc_token == NULL)
							elog(ERROR, "unterminated List structure");
					}
				}
				fc_result = (Node *) fc_l;
				break;
			}
		case RIGHT_PAREN:
			elog(ERROR, "unexpected right parenthesis");
			fc_result = NULL;		/* 保持编译器开心 */
			break;
		case OTHER_TOKEN:
			if (fc_tok_len == 0)
			{
				/* 必须是 "<>" --- 表示一个空指针 */
				fc_result = NULL;
			}
			else
			{
				elog(ERROR, "unrecognized token: \"%.*s\"", fc_tok_len, fc_token);
				fc_result = NULL;	/* 保持编译器开心 */
			}
			break;
		case T_Integer:

			/*
			 * 我们知道标记终止在一个char atoi将停止的位置
			 */
			fc_result = (Node *) makeInteger(atoi(fc_token));
			break;
		case T_Float:
			{
				char	   *fc_fval = (char *) palloc(fc_tok_len + 1);

				memcpy(fc_fval, fc_token, fc_tok_len);
				fc_fval[fc_tok_len] = '\0';
				fc_result = (Node *) makeFloat(fc_fval);
			}
			break;
		case T_Boolean:
			fc_result = (Node *) makeBoolean(fc_token[0] == 't');
			break;
		case T_String:
			/* 需要去除前导和尾随引号，以及反斜杠 */
			fc_result = (Node *) makeString(debackslash(fc_token + 1, fc_tok_len - 2));
			break;
		case T_BitString:
			{
				char	   *fc_val = palloc(fc_tok_len);

				/* 跳过前导 'b' */
				memcpy(fc_val, fc_token + 1, fc_tok_len - 1);
				fc_val[fc_tok_len - 1] = '\0';
				fc_result = (Node *) makeBitString(fc_val);
				break;
			}
		default:
			elog(ERROR, "unrecognized node type: %d", (int) fc_type);
			fc_result = NULL;		/* 保持编译器开心 */
			break;
	}

	return (void *) fc_result;
}
